/* -------------------------------------------------------------------------
 *
 * pgstat_relation.c
 *	  关系统计的实现。
 *
 * 本文件包含关系统计的实现。它与pgstat.c分开，以强制统计访问/存储实现与各类型统计的细节之间的界限。
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/pgstat_relation.c
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "catalog/partition.h"
#include "postmaster/autovacuum.h"
#include "utils/memutils.h"
#include "utils/pgstat_internal.h"
#include "utils/rel.h"
#include "utils/timestamp.h"


/* 记录在 pgstat 状态文件中持久化时写入的记录 */
typedef struct TwoPhasePgStatRecord
{
	PgStat_Counter tuples_inserted; /* 事务中插入的元组 */
	PgStat_Counter tuples_updated;	/* 事务中更新的元组 */
	PgStat_Counter tuples_deleted;	/* 事务中删除的元组 */
	/* 截断/删除之前的元组 i/u/d */
	PgStat_Counter inserted_pre_truncdrop;
	PgStat_Counter updated_pre_truncdrop;
	PgStat_Counter deleted_pre_truncdrop;
	Oid			t_id;			/* 表的 OID */
	bool		t_shared;		/* 这是一个共享目录吗？ */
	bool		t_truncdropped; /* 关系是否被截断/删除？ */
} TwoPhasePgStatRecord;


static PgStat_TableStatus *fc_pgstat_prep_relation_pending(Oid fc_rel_id, bool fc_isshared);
static void fc_add_tabstat_xact_level(PgStat_TableStatus *fc_pgstat_info, int fc_nest_level);
static void fc_ensure_tabstat_xact_level(PgStat_TableStatus *fc_pgstat_info);
static void fc_save_truncdrop_counters(PgStat_TableXactStatus *fc_trans, bool fc_is_drop);
static void fc_restore_truncdrop_counters(PgStat_TableXactStatus *fc_trans);


/*
 * 在关系之间复制统计信息。这用于类似 REINDEX 
 * CONCURRENTLY 的操作。
 */
void pgstat_copy_relation_stats(Relation fc_dst, Relation fc_src)
{
	PgStat_StatTabEntry *fc_srcstats;
	PgStatShared_Relation *fc_dstshstats;
	PgStat_EntryRef *fc_dst_ref;

	fc_srcstats = pgstat_fetch_stat_tabentry_ext(fc_src->rd_rel->relisshared,
											  RelationGetRelid(fc_src));
	if (!fc_srcstats)
		return;

	fc_dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
										  fc_dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
										  RelationGetRelid(fc_dst),
										  false);

	fc_dstshstats = (PgStatShared_Relation *) fc_dst_ref->shared_stats;
	fc_dstshstats->stats = *fc_srcstats;

	pgstat_unlock_entry(fc_dst_ref);
}

/*
 * 初始化一个 relcache 条目以计数访问统计信息。每当打开
 * 一个关系时调用。
 *
 * 我们假设 relcache 条目的 pgstat_info 字段在创建
 * relcache 条目时被 relcache.c 置为零；之后它是长期数据。
 *
 * 这并不会在共享内存中创建对统计条目的引用，也不会
 * 分配内存用于待处理的统计信息。那发生在
 * pgstat_assoc_relation() 中。
 */
void pgstat_init_relation(Relation fc_rel)
{
	char		fc_relkind = fc_rel->rd_rel->relkind;

	/*
	 * 我们只统计有存储和分区表的关系
	 */
	if (!RELKIND_HAS_STORAGE(fc_relkind) && fc_relkind != RELKIND_PARTITIONED_TABLE)
	{
		fc_rel->pgstat_enabled = false;
		fc_rel->pgstat_info = NULL;
		return;
	}

	if (!pgstat_track_counts)
	{
		if (fc_rel->pgstat_info)
			pgstat_unlink_relation(fc_rel);

		/* 我们根本不在统计 */
		fc_rel->pgstat_enabled = false;
		fc_rel->pgstat_info = NULL;
		return;
	}

	fc_rel->pgstat_enabled = true;
}

/*
 * 准备收集此关系的统计信息。
 *
 * 这确保我们在生成统计信息之前有对统计条目的引用。这很重要，
 * 因为在另一个连接中删除关系可能会导致统计条目被删除，
 * 然后在刷新统计信息时会被重新创建。
 *
 * 这与 pgstat_init_relation() 是分开的，因为
 * relcache 条目打开而不报告统计信息并不罕见。
 */
void pgstat_assoc_relation(Relation fc_rel)
{
	Assert(fc_rel->pgstat_enabled);
	Assert(fc_rel->pgstat_info == NULL);

	/* 否则找到或创建 PgStat_TableStatus 条目，并更新链接 */
	fc_rel->pgstat_info = fc_pgstat_prep_relation_pending(RelationGetRelid(fc_rel),
													fc_rel->rd_rel->relisshared);

	/* 不允许将统计信息链接到多个 relcache 条目 */
	Assert(fc_rel->pgstat_info->relation == NULL);

	/* 将此关系标记为所有者 */
	fc_rel->pgstat_info->relation = fc_rel;
}

/*
 * 打破 relcache 条目和待处理统计条目之间的相互链接。
 * 必须在链接的一端被移除时调用此函数。
 */
void pgstat_unlink_relation(Relation fc_rel)
{
	/* 如果有，移除对统计信息的链接 */
	if (fc_rel->pgstat_info == NULL)
		return;

	/* 链接状态检查 */
	Assert(fc_rel->pgstat_info->relation == fc_rel);
	fc_rel->pgstat_info->relation = NULL;
	fc_rel->pgstat_info = NULL;
}

/*
 * 如果事务中止，则确保丢弃统计信息。
 */
void pgstat_create_relation(Relation fc_rel)
{
	pgstat_create_transactional(PGSTAT_KIND_RELATION,
								fc_rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
								RelationGetRelid(fc_rel));
}

/*
 * 确保如果事务提交，则统计信息被丢弃。
 */
void pgstat_drop_relation(Relation fc_rel)
{
	int			fc_nest_level = GetCurrentTransactionNestLevel();
	PgStat_TableStatus *fc_pgstat_info;

	pgstat_drop_transactional(PGSTAT_KIND_RELATION,
							  fc_rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
							  RelationGetRelid(fc_rel));

	if (!pgstat_should_count_relation(fc_rel))
		return;

	/*
	 * 事务性地将计数器设置为 0。这确保了在事务内访问 
	 * pg_stat_xact_all_tables 显示为 0。
	 */
	fc_pgstat_info = fc_rel->pgstat_info;
	if (fc_pgstat_info->trans &&
		fc_pgstat_info->trans->nest_level == fc_nest_level)
	{
		fc_save_truncdrop_counters(fc_pgstat_info->trans, true);
		fc_pgstat_info->trans->tuples_inserted = 0;
		fc_pgstat_info->trans->tuples_updated = 0;
		fc_pgstat_info->trans->tuples_deleted = 0;
	}
}

/*
 * 报告该表刚刚被清理。
 */
void pgstat_report_vacuum(Oid fc_tableoid, bool fc_shared,
					 PgStat_Counter fc_livetuples, PgStat_Counter fc_deadtuples)
{
	PgStat_EntryRef *fc_entry_ref;
	PgStatShared_Relation *fc_shtabentry;
	PgStat_StatTabEntry *fc_tabentry;
	Oid			fc_dboid = (fc_shared ? InvalidOid : MyDatabaseId);
	TimestampTz fc_ts;

	if (!pgstat_track_counts)
		return;

	/* 将数据存储在表的哈希表条目中。 */
	fc_ts = GetCurrentTimestamp();

	/* 阻止由于与 pgstat_report_autovac() 相同的原因获取锁 */
	fc_entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
											fc_dboid, fc_tableoid, false);

	fc_shtabentry = (PgStatShared_Relation *) fc_entry_ref->shared_stats;
	fc_tabentry = &fc_shtabentry->stats;

	fc_tabentry->n_live_tuples = fc_livetuples;
	fc_tabentry->n_dead_tuples = fc_deadtuples;

	/*
	 * 非侵入性扫除可能会跳过各种页面，但我们会在这里将插入
	 * 计数器置为 0。 当前仅用于跟踪何时需要执行“插入”
	 * 自动清理，主要用于冻结新插入的元组。
	 * 将其置零可能意味着在插入足够的元组以触发另一个插入
	 * 自动清理之前，我们不会尝试再次清理表。
	 * 反包围自动清理将捕获任何持久的滞后者。
	 */
	fc_tabentry->inserts_since_vacuum = 0;

	if (IsAutoVacuumWorkerProcess())
	{
		fc_tabentry->autovac_vacuum_timestamp = fc_ts;
		fc_tabentry->autovac_vacuum_count++;
	}
	else
	{
		fc_tabentry->vacuum_timestamp = fc_ts;
		fc_tabentry->vacuum_count++;
	}

	pgstat_unlock_entry(fc_entry_ref);
}

/*
 * 报告表刚刚被分析过。
 *
 * 调用者必须提供新的活元组和死元组估计值，以及一个标志以指示是否重置 changes_since_analyze 计数器。
 */
void pgstat_report_analyze(Relation fc_rel,
					  PgStat_Counter fc_livetuples, PgStat_Counter fc_deadtuples,
					  bool fc_resetcounter)
{
	PgStat_EntryRef *fc_entry_ref;
	PgStatShared_Relation *fc_shtabentry;
	PgStat_StatTabEntry *fc_tabentry;
	Oid			fc_dboid = (fc_rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);

	if (!pgstat_track_counts)
		return;

	/*
	 * 与 VACUUM 不同，ANALYZE 可能在一个已经在目标表中插入和/或删除行的事务中运行。ANALYZE 会将这些行分别计为活元组或死元组。因为我们将在事务结束时报告这些行的计数，所以我们应该从现在所做的更新中减去这些计数，否则它们将在提交后被重复计数。（这种方法还确保如果我们中止而不是提交，shared stats 条目最终会得到正确的数字。）
	 *
	 * 对于分区表，别浪费时间。
	 */
	if (pgstat_should_count_relation(fc_rel) &&
		fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
	{
		PgStat_TableXactStatus *fc_trans;

		for (fc_trans = fc_rel->pgstat_info->trans; fc_trans; fc_trans = fc_trans->upper)
		{
			fc_livetuples -= fc_trans->tuples_inserted - fc_trans->tuples_deleted;
			fc_deadtuples -= fc_trans->tuples_updated + fc_trans->tuples_deleted;
		}
		/* 也计算已中止的子事务插入的东西 */
		fc_deadtuples -= fc_rel->pgstat_info->t_counts.t_delta_dead_tuples;
		/* 由于 ANALYZE 的计数是估计值，我们可能会出现下溢 */
		fc_livetuples = Max(fc_livetuples, 0);
		fc_deadtuples = Max(fc_deadtuples, 0);
	}

	/* 阻止由于与 pgstat_report_autovac() 相同的原因获取锁 */
	fc_entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, fc_dboid,
											RelationGetRelid(fc_rel),
											false);
	/* 不能在访问时被删除 */
	Assert(fc_entry_ref != NULL && fc_entry_ref->shared_stats != NULL);

	fc_shtabentry = (PgStatShared_Relation *) fc_entry_ref->shared_stats;
	fc_tabentry = &fc_shtabentry->stats;

	fc_tabentry->n_live_tuples = fc_livetuples;
	fc_tabentry->n_dead_tuples = fc_deadtuples;

	/*
	 * 如果被命令，将 changes_since_analyze 重置为零。这会忘记在 ANALYZE 进行过程中提交的任何更改，但我们没有好的方法来估计这些更改有多少。
	 */
	if (fc_resetcounter)
		fc_tabentry->changes_since_analyze = 0;

	if (IsAutoVacuumWorkerProcess())
	{
		fc_tabentry->autovac_analyze_timestamp = GetCurrentTimestamp();
		fc_tabentry->autovac_analyze_count++;
	}
	else
	{
		fc_tabentry->analyze_timestamp = GetCurrentTimestamp();
		fc_tabentry->analyze_count++;
	}

	pgstat_unlock_entry(fc_entry_ref);
}

/*
 * 计算插入 n 个元组的元组
 */
void pgstat_count_heap_insert(Relation fc_rel, PgStat_Counter fc_n)
{
	if (pgstat_should_count_relation(fc_rel))
	{
		PgStat_TableStatus *fc_pgstat_info = fc_rel->pgstat_info;

		fc_ensure_tabstat_xact_level(fc_pgstat_info);
		fc_pgstat_info->trans->tuples_inserted += fc_n;
	}
}

/*
 * 计算元组更新
 */
void pgstat_count_heap_update(Relation fc_rel, bool fc_hot)
{
	if (pgstat_should_count_relation(fc_rel))
	{
		PgStat_TableStatus *fc_pgstat_info = fc_rel->pgstat_info;

		fc_ensure_tabstat_xact_level(fc_pgstat_info);
		fc_pgstat_info->trans->tuples_updated++;

		/* t_tuples_hot_updated 是非事务性的，因此只需推进它 */
		if (fc_hot)
			fc_pgstat_info->t_counts.t_tuples_hot_updated++;
	}
}

/*
 * 计算元组删除
 */
void pgstat_count_heap_delete(Relation fc_rel)
{
	if (pgstat_should_count_relation(fc_rel))
	{
		PgStat_TableStatus *fc_pgstat_info = fc_rel->pgstat_info;

		fc_ensure_tabstat_xact_level(fc_pgstat_info);
		fc_pgstat_info->trans->tuples_deleted++;
	}
}

/*
 * 由于截断更新元组计数器
 */
void pgstat_count_truncate(Relation fc_rel)
{
	if (pgstat_should_count_relation(fc_rel))
	{
		PgStat_TableStatus *fc_pgstat_info = fc_rel->pgstat_info;

		fc_ensure_tabstat_xact_level(fc_pgstat_info);
		fc_save_truncdrop_counters(fc_pgstat_info->trans, false);
		fc_pgstat_info->trans->tuples_inserted = 0;
		fc_pgstat_info->trans->tuples_updated = 0;
		fc_pgstat_info->trans->tuples_deleted = 0;
	}
}

/*
 * 更新死元组计数
 *
 * 其语义是我们报告“增量”死元组的非事务性恢复；因此 t_delta_dead_tuples 减少而不是增加，而更改直接进入每个表的计数器，而不是进入事务状态。
 */
void pgstat_update_heap_dead_tuples(Relation fc_rel, int fc_delta)
{
	if (pgstat_should_count_relation(fc_rel))
	{
		PgStat_TableStatus *fc_pgstat_info = fc_rel->pgstat_info;

		fc_pgstat_info->t_counts.t_delta_dead_tuples -= fc_delta;
	}
}

/*
 * 支持 SQL 可调用的 pgstat* 函数的函数。返回一张表的收集统计数据或 NULL。NULL 并不意味着表不存在，而只是表示没有统计数据，因此调用者最好报告 ZERO。
 */
PgStat_StatTabEntry *
pgstat_fetch_stat_tabentry(Oid fc_relid)
{
	PgStat_StatTabEntry *fc_tabentry;

	fc_tabentry = pgstat_fetch_stat_tabentry_ext(false, fc_relid);
	if (fc_tabentry != NULL)
		return fc_tabentry;

	/*
	 * 如果我们没有找到它，也许它是一个共享表。
	 */
	fc_tabentry = pgstat_fetch_stat_tabentry_ext(true, fc_relid);
	return fc_tabentry;
}

/*
 * pgstat_fetch_stat_tabentry() 的更高效版本，允许指定所访问的表是否为共享关系。
 */
PgStat_StatTabEntry *
pgstat_fetch_stat_tabentry_ext(bool fc_shared, Oid fc_reloid)
{
	Oid			fc_dboid = (fc_shared ? InvalidOid : MyDatabaseId);

	return (PgStat_StatTabEntry *)
		pgstat_fetch_entry(PGSTAT_KIND_RELATION, fc_dboid, fc_reloid);
}

/*
 * 查找 rel 的任何现有 PgStat_TableStatus 条目
 *
 * 在当前数据库中查找 rel_id 的任何现有 PgStat_TableStatus 条目。如果未找到，请尝试从共享表中查找。
 *
 * 如果未找到条目，返回 NULL，不要创建新条目
 */
PgStat_TableStatus *
find_tabstat_entry(Oid fc_rel_id)
{
	PgStat_EntryRef *fc_entry_ref;

	fc_entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, fc_rel_id);
	if (!fc_entry_ref)
		fc_entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, fc_rel_id);

	if (fc_entry_ref)
		return fc_entry_ref->pending;
	return NULL;
}

/*
 * 执行关系统计特定的事务结束工作。AtEOXact_PgStat 的助手。
 *
 * 将事务插入/更新计数转移到基础 tabstat 条目中。我们不打算释放任何事务状态，因为它都在 TopTransactionContext 中，并且会自行消失。
 */
void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *fc_xact_state, bool fc_isCommit)
{
	PgStat_TableXactStatus *fc_trans;

	for (fc_trans = fc_xact_state->first; fc_trans != NULL; fc_trans = fc_trans->next)
	{
		PgStat_TableStatus *fc_tabstat;

		Assert(fc_trans->nest_level == 1);
		Assert(fc_trans->upper == NULL);
		fc_tabstat = fc_trans->parent;
		Assert(fc_tabstat->trans == fc_trans);
		/* 在事务中止的情况下恢复截断/删除前的统计数据（如果有的话） */
		if (!fc_isCommit)
			fc_restore_truncdrop_counters(fc_trans);
		/* 计算尝试的操作，无论是提交/中止 */
		fc_tabstat->t_counts.t_tuples_inserted += fc_trans->tuples_inserted;
		fc_tabstat->t_counts.t_tuples_updated += fc_trans->tuples_updated;
		fc_tabstat->t_counts.t_tuples_deleted += fc_trans->tuples_deleted;
		if (fc_isCommit)
		{
			fc_tabstat->t_counts.t_truncdropped = fc_trans->truncdropped;
			if (fc_trans->truncdropped)
			{
				/* 忘记到目前为止后端看到的存活/死掉的统计信息 */
				fc_tabstat->t_counts.t_delta_live_tuples = 0;
				fc_tabstat->t_counts.t_delta_dead_tuples = 0;
			}
			/* 插入添加一个存活的元组，删除移除一个元组 */
			fc_tabstat->t_counts.t_delta_live_tuples +=
				fc_trans->tuples_inserted - fc_trans->tuples_deleted;
			/* 更新和删除各自创建一个死元组 */
			fc_tabstat->t_counts.t_delta_dead_tuples +=
				fc_trans->tuples_updated + fc_trans->tuples_deleted;
			/* 插入、更新、删除各计为一次变更事件 */
			fc_tabstat->t_counts.t_changed_tuples +=
				fc_trans->tuples_inserted + fc_trans->tuples_updated +
				fc_trans->tuples_deleted;
		}
		else
		{
			/* 插入的元组是死的，删除的元组不受影响 */
			fc_tabstat->t_counts.t_delta_dead_tuples +=
				fc_trans->tuples_inserted + fc_trans->tuples_updated;
			/* 中止的事务不会生成changed_tuple事件 */
		}
		fc_tabstat->trans = NULL;
	}
}

/*
 * 执行关系统计特定的子事务结束工作。帮助
 * AtEOSubXact_PgStat。
 *
 * 将事务性插入/更新计数转移到下一个更高的
 * 子事务状态。
 */
void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *fc_xact_state, bool fc_isCommit, int fc_nestDepth)
{
	PgStat_TableXactStatus *fc_trans;
	PgStat_TableXactStatus *fc_next_trans;

	for (fc_trans = fc_xact_state->first; fc_trans != NULL; fc_trans = fc_next_trans)
	{
		PgStat_TableStatus *fc_tabstat;

		fc_next_trans = fc_trans->next;
		Assert(fc_trans->nest_level == fc_nestDepth);
		fc_tabstat = fc_trans->parent;
		Assert(fc_tabstat->trans == fc_trans);

		if (fc_isCommit)
		{
			if (fc_trans->upper && fc_trans->upper->nest_level == fc_nestDepth - 1)
			{
				if (fc_trans->truncdropped)
				{
					/* 将truncate/drop状态向上传播一级 */
					fc_save_truncdrop_counters(fc_trans->upper, false);
					/* 用我们的数据替换上层事务统计信息 */
					fc_trans->upper->tuples_inserted = fc_trans->tuples_inserted;
					fc_trans->upper->tuples_updated = fc_trans->tuples_updated;
					fc_trans->upper->tuples_deleted = fc_trans->tuples_deleted;
				}
				else
				{
					fc_trans->upper->tuples_inserted += fc_trans->tuples_inserted;
					fc_trans->upper->tuples_updated += fc_trans->tuples_updated;
					fc_trans->upper->tuples_deleted += fc_trans->tuples_deleted;
				}
				fc_tabstat->trans = fc_trans->upper;
				pfree(fc_trans);
			}
			else
			{
				/*
				 * 当没有即时父状态时，我们可以仅
				 * 重用记录而不是通过palloc/pfree
				 * pushup（因为这些信息都在TopTransactionContext
				 * 中）。我们必须将其重新链接到父级，
				 * 这可能意味着将新条目推入
				 * pgStatXactStack。
				 */
				PgStat_SubXactStatus *fc_upper_xact_state;

				fc_upper_xact_state = pgstat_get_xact_stack_level(fc_nestDepth - 1);
				fc_trans->next = fc_upper_xact_state->first;
				fc_upper_xact_state->first = fc_trans;
				fc_trans->nest_level = fc_nestDepth - 1;
			}
		}
		else
		{
			/*
			 * 在中止时，更新顶层表统计计数，然后忘记
			 * 子事务
			 */

			/* 首先恢复被truncate/drop覆盖的值 */
			fc_restore_truncdrop_counters(fc_trans);
			/* 计算尝试的操作，无论是提交/中止 */
			fc_tabstat->t_counts.t_tuples_inserted += fc_trans->tuples_inserted;
			fc_tabstat->t_counts.t_tuples_updated += fc_trans->tuples_updated;
			fc_tabstat->t_counts.t_tuples_deleted += fc_trans->tuples_deleted;
			/* 插入的元组是死的，删除的元组不受影响 */
			fc_tabstat->t_counts.t_delta_dead_tuples +=
				fc_trans->tuples_inserted + fc_trans->tuples_updated;
			fc_tabstat->trans = fc_trans->upper;
			pfree(fc_trans);
		}
	}
}

/*
 * 为所有待处理的事务依赖关系统计生成2PC记录。
 */
void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *fc_xact_state)
{
	PgStat_TableXactStatus *fc_trans;

	for (fc_trans = fc_xact_state->first; fc_trans != NULL; fc_trans = fc_trans->next)
	{
		PgStat_TableStatus *fc_tabstat PG_USED_FOR_ASSERTS_ONLY;
		TwoPhasePgStatRecord fc_record;

		Assert(fc_trans->nest_level == 1);
		Assert(fc_trans->upper == NULL);
		fc_tabstat = fc_trans->parent;
		Assert(fc_tabstat->trans == fc_trans);

		fc_record.tuples_inserted = fc_trans->tuples_inserted;
		fc_record.tuples_updated = fc_trans->tuples_updated;
		fc_record.tuples_deleted = fc_trans->tuples_deleted;
		fc_record.inserted_pre_truncdrop = fc_trans->inserted_pre_truncdrop;
		fc_record.updated_pre_truncdrop = fc_trans->updated_pre_truncdrop;
		fc_record.deleted_pre_truncdrop = fc_trans->deleted_pre_truncdrop;
		fc_record.t_id = fc_tabstat->t_id;
		fc_record.t_shared = fc_tabstat->t_shared;
		fc_record.t_truncdropped = fc_trans->truncdropped;

		RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
							   &fc_record, sizeof(TwoPhasePgStatRecord));
	}
}

/*
 * 我们在这里需要做的就是将事务统计状态与
 * 非事务性状态取消链接。非事务性操作计数将
 * 立即报告给统计系统，而对存活和
 * 死元组计数的影响保留在2PC状态文件中。
 *
 * 注意：AtEOXact_PgStat_Relations在PREPARE期间不会被调用。
 */
void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *fc_xact_state)
{
	PgStat_TableXactStatus *fc_trans;

	for (fc_trans = fc_xact_state->first; fc_trans != NULL; fc_trans = fc_trans->next)
	{
		PgStat_TableStatus *fc_tabstat;

		fc_tabstat = fc_trans->parent;
		fc_tabstat->trans = NULL;
	}
}

/*
 * 处理提交准备情况的2PC处理例程。
 *
 * 将保存的计数加载到我们的本地pgstats状态中。
 */
void pgstat_twophase_postcommit(TransactionId fc_xid, uint16 fc_info,
						   void *fc_recdata, uint32 fc_len)
{
	TwoPhasePgStatRecord *fc_rec = (TwoPhasePgStatRecord *) fc_recdata;
	PgStat_TableStatus *fc_pgstat_info;

	/* 为关系查找或创建表统计条目 */
	fc_pgstat_info = fc_pgstat_prep_relation_pending(fc_rec->t_id, fc_rec->t_shared);

	/* 与AtEOXact_PgStat中的计算相同，提交情况 */
	fc_pgstat_info->t_counts.t_tuples_inserted += fc_rec->tuples_inserted;
	fc_pgstat_info->t_counts.t_tuples_updated += fc_rec->tuples_updated;
	fc_pgstat_info->t_counts.t_tuples_deleted += fc_rec->tuples_deleted;
	fc_pgstat_info->t_counts.t_truncdropped = fc_rec->t_truncdropped;
	if (fc_rec->t_truncdropped)
	{
		/* 忘记到目前为止后端看到的存活/死掉的统计信息 */
		fc_pgstat_info->t_counts.t_delta_live_tuples = 0;
		fc_pgstat_info->t_counts.t_delta_dead_tuples = 0;
	}
	fc_pgstat_info->t_counts.t_delta_live_tuples +=
		fc_rec->tuples_inserted - fc_rec->tuples_deleted;
	fc_pgstat_info->t_counts.t_delta_dead_tuples +=
		fc_rec->tuples_updated + fc_rec->tuples_deleted;
	fc_pgstat_info->t_counts.t_changed_tuples +=
		fc_rec->tuples_inserted + fc_rec->tuples_updated +
		fc_rec->tuples_deleted;
}

/*
 * 处理回滚准备情况的2PC处理例程。
 *
 * 将保存的计数加载到我们的本地pgstats状态中，但将其
 * 视为已中止。
 */
void pgstat_twophase_postabort(TransactionId fc_xid, uint16 fc_info,
						  void *fc_recdata, uint32 fc_len)
{
	TwoPhasePgStatRecord *fc_rec = (TwoPhasePgStatRecord *) fc_recdata;
	PgStat_TableStatus *fc_pgstat_info;

	/* 为关系查找或创建表统计条目 */
	fc_pgstat_info = fc_pgstat_prep_relation_pending(fc_rec->t_id, fc_rec->t_shared);

	/* 与AtEOXact_PgStat中的计算相同，中止情况 */
	if (fc_rec->t_truncdropped)
	{
		fc_rec->tuples_inserted = fc_rec->inserted_pre_truncdrop;
		fc_rec->tuples_updated = fc_rec->updated_pre_truncdrop;
		fc_rec->tuples_deleted = fc_rec->deleted_pre_truncdrop;
	}
	fc_pgstat_info->t_counts.t_tuples_inserted += fc_rec->tuples_inserted;
	fc_pgstat_info->t_counts.t_tuples_updated += fc_rec->tuples_updated;
	fc_pgstat_info->t_counts.t_tuples_deleted += fc_rec->tuples_deleted;
	fc_pgstat_info->t_counts.t_delta_dead_tuples +=
		fc_rec->tuples_inserted + fc_rec->tuples_updated;
}

/*
 * 刷新条目的待处理统计信息
 *
 * 如果nowait为真，则该函数在无法立即获取锁时返回false，否则返回true。
 *
 * 一些统计信息在成功刷新时被复制到相应的待处理数据库统计
 * 条目中。
 */
bool pgstat_relation_flush_cb(PgStat_EntryRef *fc_entry_ref, bool fc_nowait)
{
	static const PgStat_TableCounts fc_all_zeroes;
	Oid			fc_dboid;
	PgStat_TableStatus *fc_lstats; /* 待处理统计信息条目 */
	PgStatShared_Relation *fc_shtabstats;
	PgStat_StatTabEntry *fc_tabentry;	/* 共享统计信息的表条目 */
	PgStat_StatDBEntry *fc_dbentry;	/* 待处理数据库条目 */

	fc_dboid = fc_entry_ref->shared_entry->key.dboid;
	fc_lstats = (PgStat_TableStatus *) fc_entry_ref->pending;
	fc_shtabstats = (PgStatShared_Relation *) fc_entry_ref->shared_stats;

	/*
	 * 忽略没有累积任何实际计数的条目，例如
	 * 由规划器打开但未使用的索引。
	 */
	if (memcmp(&fc_lstats->t_counts, &fc_all_zeroes,
			   sizeof(PgStat_TableCounts)) == 0)
	{
		return true;
	}

	if (!pgstat_lock_entry(fc_entry_ref, fc_nowait))
		return false;

	/* 将值添加到共享条目。 */
	fc_tabentry = &fc_shtabstats->stats;

	fc_tabentry->numscans += fc_lstats->t_counts.t_numscans;
	fc_tabentry->tuples_returned += fc_lstats->t_counts.t_tuples_returned;
	fc_tabentry->tuples_fetched += fc_lstats->t_counts.t_tuples_fetched;
	fc_tabentry->tuples_inserted += fc_lstats->t_counts.t_tuples_inserted;
	fc_tabentry->tuples_updated += fc_lstats->t_counts.t_tuples_updated;
	fc_tabentry->tuples_deleted += fc_lstats->t_counts.t_tuples_deleted;
	fc_tabentry->tuples_hot_updated += fc_lstats->t_counts.t_tuples_hot_updated;

	/*
	 * 如果表被截断/删除，首先重置活跃/死亡计数器。
	 */
	if (fc_lstats->t_counts.t_truncdropped)
	{
		fc_tabentry->n_live_tuples = 0;
		fc_tabentry->n_dead_tuples = 0;
		fc_tabentry->inserts_since_vacuum = 0;
	}

	fc_tabentry->n_live_tuples += fc_lstats->t_counts.t_delta_live_tuples;
	fc_tabentry->n_dead_tuples += fc_lstats->t_counts.t_delta_dead_tuples;
	fc_tabentry->changes_since_analyze += fc_lstats->t_counts.t_changed_tuples;
	fc_tabentry->inserts_since_vacuum += fc_lstats->t_counts.t_tuples_inserted;
	fc_tabentry->blocks_fetched += fc_lstats->t_counts.t_blocks_fetched;
	fc_tabentry->blocks_hit += fc_lstats->t_counts.t_blocks_hit;

	/* 限制 n_live_tuples 以防 delta_live_tuples 为负数 */
	fc_tabentry->n_live_tuples = Max(fc_tabentry->n_live_tuples, 0);
	/* 同样适用于 n_dead_tuples */
	fc_tabentry->n_dead_tuples = Max(fc_tabentry->n_dead_tuples, 0);

	pgstat_unlock_entry(fc_entry_ref);

	/* 条目已成功刷新，将其添加到数据库统计信息中 */
	fc_dbentry = pgstat_prep_database_pending(fc_dboid);
	fc_dbentry->n_tuples_returned += fc_lstats->t_counts.t_tuples_returned;
	fc_dbentry->n_tuples_fetched += fc_lstats->t_counts.t_tuples_fetched;
	fc_dbentry->n_tuples_inserted += fc_lstats->t_counts.t_tuples_inserted;
	fc_dbentry->n_tuples_updated += fc_lstats->t_counts.t_tuples_updated;
	fc_dbentry->n_tuples_deleted += fc_lstats->t_counts.t_tuples_deleted;
	fc_dbentry->n_blocks_fetched += fc_lstats->t_counts.t_blocks_fetched;
	fc_dbentry->n_blocks_hit += fc_lstats->t_counts.t_blocks_hit;

	return true;
}

void pgstat_relation_delete_pending_cb(PgStat_EntryRef *fc_entry_ref)
{
	PgStat_TableStatus *fc_pending = (PgStat_TableStatus *) fc_entry_ref->pending;

	if (fc_pending->relation)
		pgstat_unlink_relation(fc_pending->relation);
}

/*
 * 查找或创建一个 PgStat_TableStatus 条目对于 rel。如果不存在，则创建并初始化新条目。
 */
static PgStat_TableStatus *
fc_pgstat_prep_relation_pending(Oid fc_rel_id, bool fc_isshared)
{
	PgStat_EntryRef *fc_entry_ref;
	PgStat_TableStatus *fc_pending;

	fc_entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
										  fc_isshared ? InvalidOid : MyDatabaseId,
										  fc_rel_id, NULL);
	fc_pending = fc_entry_ref->pending;
	fc_pending->t_id = fc_rel_id;
	fc_pending->t_shared = fc_isshared;

	return fc_pending;
}

/*
 * 添加一个新的（子）事务状态记录
 */
static void fc_add_tabstat_xact_level(PgStat_TableStatus *fc_pgstat_info, int fc_nest_level)
{
	PgStat_SubXactStatus *fc_xact_state;
	PgStat_TableXactStatus *fc_trans;

	/*
	 * 如果这是当前嵌套级别中要修改的第一个 rel，我们
	 * 首先必须推送一个事务栈条目。
	 */
	fc_xact_state = pgstat_get_xact_stack_level(fc_nest_level);

	/* 现在为每个表创建一个栈条目 */
	fc_trans = (PgStat_TableXactStatus *)
		MemoryContextAllocZero(TopTransactionContext,
							   sizeof(PgStat_TableXactStatus));
	fc_trans->nest_level = fc_nest_level;
	fc_trans->upper = fc_pgstat_info->trans;
	fc_trans->parent = fc_pgstat_info;
	fc_trans->next = fc_xact_state->first;
	fc_xact_state->first = fc_trans;
	fc_pgstat_info->trans = fc_trans;
}

/*
 * 如有必要，添加一个新的（子）事务记录。
 */
static void fc_ensure_tabstat_xact_level(PgStat_TableStatus *fc_pgstat_info)
{
	int			fc_nest_level = GetCurrentTransactionNestLevel();

	if (fc_pgstat_info->trans == NULL ||
		fc_pgstat_info->trans->nest_level != fc_nest_level)
		fc_add_tabstat_xact_level(fc_pgstat_info, fc_nest_level);
}

/*
 * 每当表被截断/删除时，我们保存其 i/u/d 计数器，以便
 * 可以清除它们，如果执行截断/删除的（子）事务稍后中止，
 * 计数器可以恢复到保存的（截断/删除前）值。
 *
 * 请注意，对于截断，我们只在特定子事务级别的第一次截断时执行此操作。
 */
static void fc_save_truncdrop_counters(PgStat_TableXactStatus *fc_trans, bool fc_is_drop)
{
	if (!fc_trans->truncdropped || fc_is_drop)
	{
		fc_trans->inserted_pre_truncdrop = fc_trans->tuples_inserted;
		fc_trans->updated_pre_truncdrop = fc_trans->tuples_updated;
		fc_trans->deleted_pre_truncdrop = fc_trans->tuples_deleted;
		fc_trans->truncdropped = true;
	}
}

/*
 * 当截断中止时恢复计数器
 */
static void fc_restore_truncdrop_counters(PgStat_TableXactStatus *fc_trans)
{
	if (fc_trans->truncdropped)
	{
		fc_trans->tuples_inserted = fc_trans->inserted_pre_truncdrop;
		fc_trans->tuples_updated = fc_trans->updated_pre_truncdrop;
		fc_trans->tuples_deleted = fc_trans->deleted_pre_truncdrop;
	}
}
